#include <locale>
#include <map>
#include <functional>
#include <string>
#include <string_view>

#include "httpServerConf.h"
#include "httpHeaderConf.h"
#include "connPoolConf.h"

#include "co/co/pool.h"
#include "co/http.h"
#include "co/json.h"
#include "co/fs.h"
#include "co/fastring.h"

#include "mysqlx/devapi/common.h"
#include "mysqlx/devapi/result.h"
#include "mysqlx/devapi/row.h"
#include "mysqlx/xdevapi.h"

namespace gd__ {
    std::map<std::string, std::function<void(http::Req const &, http::Res &)>> httpServerConf::methods {
        {httpHeaderConf::kInsert, onInsert},
        {httpHeaderConf::kDelete, onDelete},
        {httpHeaderConf::kSelect, onSelect},
        {httpHeaderConf::kAddImg, onAddImg},
        {httpHeaderConf::kDelImg, onDelImg}
    };

    void httpServerConf::httpOnReq(http::Req const & req, http::Res & res) {
        res.set_status(200);

        std::cout << "method is : " << req.method() << '\n';

        if (req.is_method_post()) {
            if (std::string_view(req.header(httpHeaderConf::kOperation)).empty()) {
                addFailedHeader(res, "wrong operation");
            } else {
                methods[req.header(httpHeaderConf::kOperation)](req, res);
            }
        } else {
            addFailedHeader(res, "wrong method");
        }
    }

    void httpServerConf::onInsert(http::Req const & req, http::Res & res) {
        json::Json userData(json::parse(req.body()));

        mysqlx::Session * sess {
            static_cast<mysqlx::Session *>(gd__::connPoolConf::connPool.pop())
        };

        mysqlx::Table userTb(
            sess->getSchema(gd__::connPoolConf::db)
                .getTable(gd__::connPoolConf::tb)
        );

        sess->startTransaction();

        try {
            userTb
            .insert(
                httpHeaderConf::kName, 
                httpHeaderConf::kPhone, 
                httpHeaderConf::kId, 
                httpHeaderConf::kImageSrc
            )
            .values(
                userData.get(httpHeaderConf::kName).as_c_str(),
                userData.get(httpHeaderConf::kPhone).as_c_str(),
                userData.get(httpHeaderConf::kId).as_c_str(),
                userData.get(httpHeaderConf::kImageSrc).as_c_str()
            )
            .execute();

            sess->commit();

            addSuccessHeader(res);
        } catch (mysqlx::Error const & e) {
            sess->rollback();

            addFailedHeader(res, e.what());

            std::cerr << e.what() << '\n';
        }

        gd__::connPoolConf::connPool.push(static_cast<void*>(sess));
    }

    void httpServerConf::onDelete(const http::Req & req, http::Res & res) {
        if (std::string_view(req.header(httpHeaderConf::kName)).empty()) {
            addFailedHeader(res, "no name option");

            return;
        }

        mysqlx::Session * sess {
            static_cast<mysqlx::Session *>(gd__::connPoolConf::connPool.pop())
        };

        mysqlx::Table userTb(
            sess->getSchema(gd__::connPoolConf::db)
                .getTable(gd__::connPoolConf::tb)
        );

        sess->startTransaction();

        try {
            userTb
            .remove()
            .where("name = :param")
            .bind("param", req.header(httpHeaderConf::kName))
            .execute();

            sess->commit();

            addSuccessHeader(res);
        } catch (mysqlx::Error const & e) {
            sess->rollback();

            addFailedHeader(res, e.what());

            std::cerr << e.what() << '\n';
        }

        gd__::connPoolConf::connPool.push(static_cast<void*>(sess));
    }

    void httpServerConf::onSelect(const http::Req & req, http::Res & res) {
        if (std::string_view(req.header(httpHeaderConf::kName)).empty()) {
            addFailedHeader(res, "no name option");

            return;
        }
        json::Json jsonArrWrapper(json::object());
        json::Json sqlResult(json::array());

         mysqlx::Session * sess {
            static_cast<mysqlx::Session *>(gd__::connPoolConf::connPool.pop())
        };

        mysqlx::Table userTb(
            sess->getSchema(gd__::connPoolConf::db)
                .getTable(gd__::connPoolConf::tb)
        );

        fastring nameParam("%");
        nameParam += req.header(httpHeaderConf::kName);
        nameParam += "%";

        try {
            auto rowsData(
                userTb
                    .select(
                        httpHeaderConf::kName, 
                        httpHeaderConf::kPhone, 
                        httpHeaderConf::kId, 
                        httpHeaderConf::kImageSrc
                    )
                    .where("name like :param")
                    .bind("param", nameParam.c_str())
                    .execute()
            );

            mysqlx::Row rowData;

            while ((rowData = rowsData.fetchOne())) {
                json::Json jsonFromRowData(json::object());

                jsonFromRowData.add_member(
                    httpHeaderConf::kName, 
                    rowData[0].get<std::string>()
                );
                jsonFromRowData.add_member(
                    httpHeaderConf::kPhone,
                    rowData[1].get<std::string>()
                );
                jsonFromRowData.add_member(
                    httpHeaderConf::kId,
                    rowData[2].get<std::string>()
                );
                jsonFromRowData.add_member(
                    httpHeaderConf::kImageSrc,
                    rowData[3].get<std::string>()
                );

                sqlResult.push_back(std::move(jsonFromRowData));
            }

            addSuccessHeader(res);

            jsonArrWrapper.add_member("userList", std::move(sqlResult));

            res.set_body(jsonArrWrapper.pretty());
        } catch (mysqlx::Error const & e) {
            addFailedHeader(res, e.what());

            std::cerr << e.what() << '\n';
        }

        gd__::connPoolConf::connPool.push(static_cast<void*>(sess));
    }

    void httpServerConf::onAddImg(http::Req const & req, http::Res & res) {
        if (std::string_view(req.header(httpHeaderConf::kImageSrc)).empty()) {
            addFailedHeader(res, "no imagesrc option");

            return;
        }

        std::cout << "start write file\n";

        fs::file imageFile(req.header(httpHeaderConf::kImageSrc), 'w');
        imageFile.write(req.body(), req.body_size());
        imageFile.close();

        std::cout << "end write file\n";

        addSuccessHeader(res);
    }

    void httpServerConf::onDelImg(const http::Req & req, http::Res & res) {
        if (std::string_view(req.header(httpHeaderConf::kImageSrc)).empty()) {
            addFailedHeader(res, "no imagesrc option");

            return;
        }

        fs::remove(req.header(httpHeaderConf::kImageSrc));

        addSuccessHeader(res);
    }

    void httpServerConf::addSuccessHeader(http::Res & res) {
        res.add_header(
            httpHeaderConf::kResult,
            httpHeaderConf::kSuccess
        );

        res.add_header(
            "Access-Control-Allow-Origin",
            "*"
        );

        res.add_header(
            "Access-Control-Allow-Headers",
            "*"
        );

        res.add_header(
            "Access-Control-Expose-Headers",
            "*"
        );
    }

    void httpServerConf::addFailedHeader(http::Res & res, std::string const & reason) {
        res.add_header(
            httpHeaderConf::kResult,
            httpHeaderConf::kFailed
        );

        res.add_header(
            httpHeaderConf::kReason,
            reason.c_str()
        );

        res.add_header(
            "Access-Control-Allow-Origin",
            "*"
        );

        res.add_header(
            "Access-Control-Allow-Headers",
            "*"
        );

        res.add_header(
            "Access-Control-Expose-Headers",
            "*"
        );
    }
}